package com.xyy.saas.payment.service.impl;

import com.xyy.ec.merchant.bussiness.dto.account.LoginAccountDto;
import com.xyy.saas.payment.config.SpringContextHolder;
import com.xyy.saas.payment.constants.Constants;
import com.xyy.saas.payment.constants.HttpStatus;
import com.xyy.saas.payment.cores.enums.JDPayContractStatusEnum;
import com.xyy.saas.payment.dao.model.*;
import com.xyy.saas.payment.dto.BankBillJd0028Total;
import com.xyy.saas.payment.exception.PaymentException;
import com.xyy.saas.payment.manage.cores.dto.*;
import com.xyy.saas.payment.manage.cores.enums.ResultCodeEnum;
import com.xyy.saas.payment.manage.cores.vo.BankCardBindLogVo;
import com.xyy.saas.payment.manage.cores.vo.ResultPageVo;
import com.xyy.saas.payment.manage.cores.vo.ResultVO;
import com.xyy.saas.payment.rpc.EcMerchantRemote;
import com.xyy.saas.payment.service.*;
import com.xyy.saas.payment.service.jd.JDBillService;
import com.xyy.saas.payment.service.job.FBankSFTPManage;
import com.xyy.saas.payment.service.job.impl.ChannelSftpService;
import com.xyy.saas.payment.util.ApolloUtil;
import com.xyy.saas.payment.util.DateUtil;
import com.xyy.saas.payment.util.PojoConvertUtil;
import com.xyy.saas.payment.util.jdpay.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class JDPayServiceImpl implements JDPayService {
    @Value("#{'${jd.pay.send.pingan.mntNo:1}'.split(',')}")
    private List<String> jdPaySendPinganMntNo;
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private ChannelSftpService channelSftpService;
    @Autowired
    private BankBillJd0026Service bankBillJd0026Service;
    @Autowired
    private BankBillJd0027Service bankBillJd0027Service;
    @Autowired
    private BankBillJd0028Service bankBillJd0028Service;
    @Autowired
    private BankCardBindService bankCardBindService;
    @Autowired
    private EcMerchantRemote ecMerchantRemote;
    @Autowired
    private BankCardBindLogService bankCardBindLogService;
    @Autowired
    private BankBillSubmitPinganService bankBillSubmitPinganService;

    private static final String SPLIT = "------------------------------";
    /**
     * 京东账单处理
     * 1、下载账单并解析
     * 2、上报数据给平安
     */
    @Override
    public String checkBill(String date) {
        if (StringUtils.isBlank(date)) {
            date = DateUtil.format(new Date(), DateUtil.DATEFORMATDAY_2);
        }
        StringBuilder sb = new StringBuilder();
        Set<Map.Entry<String, JDBillService>> jdBillServices = SpringContextHolder.getApplicationContext().getBeansOfType(JDBillService.class).entrySet();
        for (Map.Entry<String, JDBillService> jdBillService : jdBillServices) {
            try {
                String msg = jdBillService.getValue().checkBill(date);
                if (StringUtils.isNotBlank(msg)) {
                    sb.append(jdBillService.getKey()).append(msg);
                }

            } catch (Exception e) {
                log.error("账单checkBill {}",jdBillService.getKey(), e);
            }
        }
        return sb.toString();
    }
    /**
     * 京东账单处理
     * 1、下载账单并解析
     * 2、上报数据给平安
     */
    @Override
    public String billDeal(String logPrefix, String date, boolean retry) {
        log.info("{} [jdPay]账单处理: JDPayService#billDeal start", logPrefix);
        long start = System.currentTimeMillis();
        if (StringUtils.isBlank(date)) {
            date = DateUtil.format(new Date(), DateUtil.DATEFORMATDAY_2);
        }
        // 分布式锁
        String lockKey = "xyy_billDeal_execute_" + date;
        if (!redisTemplate.opsForValue().setIfAbsent(lockKey, "default_value")) {
            log.info("{} [jdPay]账单处理: JDPayService#billDeal未获取到锁，不进行处理...", logPrefix);
            return "";
        }
        StringJoiner returnMsg = new StringJoiner("\r\n");
        returnMsg.add("");
        try {
            redisTemplate.expire(lockKey, 30, TimeUnit.MINUTES);
            // 当天执行成功后保存的key，判断当日是否已成功执行
            String sucKey = lockKey + "_suc";
            Object result = redisTemplate.opsForValue().get(sucKey);
            if (null != result && "success".equals(result.toString()) && !retry) {
                log.info("{} [jdPay]账单处理: 本日任务已处理成功，不进行重复处理：{}", logPrefix, sucKey);
                return "";
            }
            // 下载并解析账单
            downAndAnalysisBill(logPrefix, date);
            // 报送账单到平安
            for (String merchant : jdPaySendPinganMntNo) {
                if (StringUtils.isNotBlank(merchant)) {
                    returnMsg.add(submitBillPingan(logPrefix, System.currentTimeMillis() - start, merchant, date));
                    returnMsg.add(SPLIT);
                }
            }
            // 执行成功，保存记录，不进行重复处理
            redisTemplate.opsForValue().set(sucKey, "success", 2, TimeUnit.HOURS);
        } catch (Exception e) {
            log.error("{} [jdPay]账单处理: JDPayService#billDeal_fail : ", logPrefix, e);
            returnMsg.add("京东支付账单处理失败。失败信息：" + e.getMessage());
        } finally {
            redisTemplate.delete(lockKey);
        }
        log.info("{} [jdPay]账单处理: JDPayService#billDeal_end 耗时 {} ", logPrefix, System.currentTimeMillis() - start);
        return cardBindTotal() + returnMsg.toString();
    }

    /**
     * 统计绑卡信息
     */
    private String cardBindTotal() {
        String dayOffset = DateUtil.getDayOffset(DateUtil.DATEFORMATDAY, new Date(), -1);
        StringJoiner cardBind = new StringJoiner("\r\n");
        cardBind.add("").add(SPLIT);
        cardBind.add("绑卡统计(" + dayOffset + ")：");
        BankCardBindQueryParam bankCardBindQueryParam = BankCardBindQueryParam.builder().updateTimeStart(dayOffset).updateTimeEnd(dayOffset).build();

        bankCardBindQueryParam.setContractStatus(JDPayContractStatusEnum.SUCC.getCode());
        bankCardBindQueryParam.setCardType("DE");
        long sucDeCount = bankCardBindService.total(bankCardBindQueryParam);
        cardBind.add("绑卡(储蓄卡)：" + sucDeCount);

        bankCardBindQueryParam.setCardType("CR");
        long sucCrCount = bankCardBindService.total(bankCardBindQueryParam);
        cardBind.add("绑卡(信用卡)：" + sucCrCount);

        bankCardBindQueryParam.setContractStatus(JDPayContractStatusEnum.REMO.getCode());
        bankCardBindQueryParam.setCardType("DE");
        long remoteDeCount = bankCardBindService.total(bankCardBindQueryParam);
        cardBind.add("解绑(储蓄卡)：" + remoteDeCount);

        bankCardBindQueryParam.setCardType("CR");
        long remoteCrCount = bankCardBindService.total(bankCardBindQueryParam);
        cardBind.add("解绑(信用卡)：" + remoteCrCount);

        cardBind.add(SPLIT);
        return cardBind.toString();
    }

    /**
     * 下载并解析账单
     */
    @Override
    public void downAndAnalysisBill(String logPrefix, String date) throws Exception {
        Set<Map.Entry<String, JDBillService>> jdBillServices = SpringContextHolder.getApplicationContext().getBeansOfType(JDBillService.class).entrySet();
        for (Map.Entry<String, JDBillService> jdBillService : jdBillServices) {
            long start = System.currentTimeMillis();
            // 若失败，则重试，最多重试三次
            for (int i = 0; i < 3; i++) {
                try {
                    jdBillService.getValue().downAndAnalysisBill(logPrefix, date);
                    break;
                } catch (Exception e) {
                    log.error("{} [jdPay]账单处理: downAndAnalysisBill#execute {} : 第{}次处理失败：{}", logPrefix, jdBillService.getKey(), i + 1, e);
                    if (i >= 2) {
                        throw e;
                    }
                    Thread.sleep(5 * 1000);
                }
            }
            log.info("{} [jdPay]账单处理: downAndAnalysisBill#execute {} : 处理完成，耗时 {}", logPrefix, jdBillService.getKey(), System.currentTimeMillis() - start);
        }
    }

    /**
     * 上报数据给平安
     */
    @Override
    public String submitBillPingan(String logPrefix, long analyTime, String merchantNo, String date) throws Exception {
        long start = System.currentTimeMillis();
        if (StringUtils.isBlank(date)) {
            date = DateUtil.format(new Date(), "yyyyMMdd");
        }
        // 获取昨天日期
        String beforeDate = DateUtil.getDayOffset("yyyyMMdd", date, -1);
        String downPath = getDownPath(date);
        // 文件名称
        String fileName = beforeDate + "_" + merchantNo + "_TRANSDETAIL.txt";
        String returnMsg = "";
        log.info("{} [jdPay]账单处理: submitBillPingan#execute 报送平安文件开始 {} - {} - {}", logPrefix, date, beforeDate, downPath + File.separator + fileName);
        // 若失败，则重试，最多重试三次
        for (int i = 0; i < 3; i++) {
            try {
                File file = new File(downPath + File.separator + fileName);
                if (file.exists()) {
                    file.delete();
                }
                // 删除SFTP文件
                channelSftpService.deleteFile(logPrefix, FBankSFTPManage.KEY_PINGAN, "", fileName);
                // 账单head数据
                String totalHead = fillPinganHead(logPrefix, merchantNo, downPath, fileName, beforeDate, date);
                // 账单明细数据
                fillPinganDetail(logPrefix, merchantNo, downPath, fileName, date);
                // 上传文件到SFTP
                channelSftpService.upload(logPrefix, FBankSFTPManage.KEY_PINGAN, downPath + File.separator + fileName, "", fileName);
                // 记录日志
                returnMsg = "京东支付账单处理完成：\r\n二级商户号：" + transMerchant(merchantNo) + "，解析账单耗时：" + analyTime + "ms，报送平安耗时：" + (System.currentTimeMillis() - start) + "ms，报送平安账单头信息：" + totalHead;
                log.info("{} [jdPay]账单处理: JDBillService#billDeal_success：解析耗时{}ms，报送平安耗时{}ms，报送平安Head:{}", logPrefix, analyTime, System.currentTimeMillis() - start, totalHead);
                break;
            } catch (Exception e) {
                log.error("{} [jdPay]账单处理: submitBillPingan#execute : 第{}次处理失败：{}", logPrefix, i + 1, e);
                if (i >= 2) {
                    throw e;
                }
                Thread.sleep(5 * 1000);
            }
        }
        log.info("{} [jdPay]账单处理: submitBillPingan#execute 报送平安结束，耗时 {} ms", logPrefix, System.currentTimeMillis() - start);
        return returnMsg;
    }

    @Override
    public ResultPageVo<List<BankBillJd0026Dto>> queryByPage0026(BankBillJd0026QueryParam param) throws PaymentException {
        // 参数校验
        if (null == param || StringUtils.isBlank(param.getMerchantNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        // 分页转换
        param.setPage(null == param.getPage() ? 1 : param.getPage());
        param.setPageSize(null == param.getPageSize() ? 20 : param.getPageSize());
        // 查询出总数
        long total = bankBillJd0026Service.total(param);
        log.info("queryByPage total:{}", total);
        if (0 == total) {
            return ResultPageVo.createSuccess();
        }
        // 查询数据
        List<BankBillJd0026> bankBillJd0026s = bankBillJd0026Service.queryPage(param);
        if (null != bankBillJd0026s) {
            for (BankBillJd0026 bankBillJd0026 : bankBillJd0026s) {
                bankBillJd0026.setMerchantNo(transMerchant(bankBillJd0026.getMerchantNo()));
            }
        }
        return ResultPageVo.createSuccess(param.getPage(), param.getPageSize(), total, PojoConvertUtil.copyList(bankBillJd0026s, BankBillJd0026Dto::new));
    }

    @Override
    public ResultPageVo<List<BankBillJd0027Dto>> queryByPage0027(BankBillJd0027QueryParam param) throws PaymentException {
        // 参数校验
        if (null == param || StringUtils.isBlank(param.getMerchantNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        // 分页转换
        param.setPage(null == param.getPage() ? 1 : param.getPage());
        param.setPageSize(null == param.getPageSize() ? 20 : param.getPageSize());
        // 查询出总数
        long total = bankBillJd0027Service.total(param);
        log.info("queryByPage total:{}", total);
        if (0 == total) {
            return ResultPageVo.createSuccess();
        }
        // 查询数据
        List<BankBillJd0027> bankBillJd0027s = bankBillJd0027Service.queryPage(param);
        if (null != bankBillJd0027s) {
            for (BankBillJd0027 bankBillJd0027 : bankBillJd0027s) {
                bankBillJd0027.setMerchantNo(transMerchant(bankBillJd0027.getMerchantNo()));
            }
        }
        return ResultPageVo.createSuccess(param.getPage(), param.getPageSize(), total, PojoConvertUtil.copyList(bankBillJd0027s, BankBillJd0027Dto::new));
    }

    @Override
    public ResultPageVo<List<BankBillJd0028Dto>> queryByPage0028(BankBillJd0028QueryParam param) throws PaymentException {
        // 参数校验
        if (null == param || StringUtils.isBlank(param.getMerchantNo())) {
            throw new PaymentException(ResultCodeEnum.PARAM_ERROR);
        }
        // 分页转换
        param.setPage(null == param.getPage() ? 1 : param.getPage());
        param.setPageSize(null == param.getPageSize() ? 20 : param.getPageSize());
        // 查询出总数
        long total = bankBillJd0028Service.total(param);
        log.info("queryByPage total:{}", total);
        if (0 == total) {
            return ResultPageVo.createSuccess();
        }
        // 查询数据
        List<BankBillJd0028> bankBillJd0028s = bankBillJd0028Service.queryPage(param);
        if (null != bankBillJd0028s) {
            for (BankBillJd0028 bankBillJd0028 : bankBillJd0028s) {
                bankBillJd0028.setMerchantNo(transMerchant(bankBillJd0028.getMerchantNo()));
            }
        }
        return ResultPageVo.createSuccess(param.getPage(), param.getPageSize(), total, PojoConvertUtil.copyList(bankBillJd0028s, BankBillJd0028Dto::new));
    }

    /**
     * 查询绑卡日志记录
     */
    @Override
    public ResultVO<List<BankCardBindLogDto>> queryCardLog(BankCardBindLogVo param) throws IllegalAccessException {
        // 1、参数校验
        PojoConvertUtil.checkBlank(param, Arrays.asList("contractReqNo"));
        // 2、查询数据
        List<BankCardBindLog> bankCardBindLogs = bankCardBindLogService.query(BankCardBindLog.builder().contractReqNo(param.getContractReqNo()).build());
        List<BankCardBindLogDto> bankCardBindLogDtos = PojoConvertUtil.copyList(bankCardBindLogs, BankCardBindLogDto::new);
        for (BankCardBindLogDto bankCardBindLogDto : bankCardBindLogDtos) {
            JDPayContractStatusEnum jdPayContractStatusEnum = JDPayContractStatusEnum.getEnum(bankCardBindLogDto.getContractStatus());
            if (null != jdPayContractStatusEnum) {
                bankCardBindLogDto.setOperMsg(bankCardBindLogDto.getTerminalType() + "-" + bankCardBindLogDto.getContractStyle() + "-" + jdPayContractStatusEnum.getDesc() + "银行卡");
            }
        }
        return ResultVO.createSuccess(bankCardBindLogDtos);
    }

    /**
     * 查询绑卡列表
     */
    @Override
    public ResultPageVo<List<BankCardBindDto>> queryCardPage(BankCardBindQueryParam param) {
        param = (null == param ? new BankCardBindQueryParam() : param);
        if (StringUtils.isNotBlank(param.getCardNo()) && param.getCardNo().length() > 4) {
            param.setCardNo(param.getCardNo().substring(param.getCardNo().length() - 4));
        }
        // 转换手机号为accountId
        if (StringUtils.isNotBlank(param.getBindAccount())) {
            LoginAccountDto loginAccountDto = ecMerchantRemote.queryMerchantByPhone(param.getBindAccount());
            if (null == loginAccountDto || null == loginAccountDto.getId()) {
                return ResultPageVo.createSuccess();
            }
            param.setBindAccount(String.valueOf(loginAccountDto.getId()));
        }
        // 分页转换
        param.setPage(null == param.getPage() ? 1 : param.getPage());
        param.setPageSize(null == param.getPageSize() ? 20 : param.getPageSize());
        // 查询出总数
        long total = bankCardBindService.total(param);
        log.info("queryByPage total:{}", total);
        if (0 == total) {
            return ResultPageVo.createSuccess();
        }
        // 查询数据
        List<Long> accountIds = new ArrayList<>();
        List<BankCardBind> bankCardBinds = bankCardBindService.queryPage(param);
        List<BankCardBindDto> bankCardBindDtos = PojoConvertUtil.copyList(bankCardBinds, BankCardBindDto::new);
        for (BankCardBindDto bankCardBindDto : bankCardBindDtos) {
            bankCardBindDto.trans();
            try {
                accountIds.add(Long.valueOf(bankCardBindDto.getBindAccount()));
            } catch (Exception e) {
            }
        }
        // 通过bindAccount获取手机号
        Map<String, String> maps = ecMerchantRemote.queryPhoneByAccountId(accountIds);
        for (BankCardBindDto bankCardBindDto : bankCardBindDtos) {
            if (maps.containsKey(bankCardBindDto.getBindAccount())) {
                bankCardBindDto.setBindAccount(maps.get(bankCardBindDto.getBindAccount()));
            }
        }
        return ResultPageVo.createSuccess(param.getPage(), param.getPageSize(), total, bankCardBindDtos);
    }

    /**
     * 导出账单_0026
     */
    @Override
    public void export0026(BankBillJd0026QueryParam param, HttpServletRequest request, HttpServletResponse response) throws ParseException, IOException {
        // 1、参数赋值与校验
        if (null == param || StringUtils.isBlank(param.getMerchantNo()) || StringUtils.isBlank(param.getSettleFinishTimeStart()) || StringUtils.isBlank(param.getSettleFinishTimeEnd())) {
            log.error("export0026参数错误");
            response.setStatus(HttpStatus.PARAM_ERROR);
            return;
        }
        if (DateUtil.dateDif(param.getSettleFinishTimeStart(), param.getSettleFinishTimeEnd()) > 31) {
            log.error("export0026只允许导出31天内的数据");
            response.setStatus(HttpStatus.PARAM_ERROR);
            return;
        }
        String downName = "shareAccountWater_" + param.getSettleFinishTimeStart().replace("-", "") + "_" + param.getSettleFinishTimeEnd().replace("-", "") + ".csv";
        // 2、初始化 HttpServletResponse
        initHttpResponse(response, downName);
        // 3、循环写入数据 到 HttpServletResponse
        OutputStream os = response.getOutputStream();
        try {
            writeHttpResponse(response, os, Constants.JD_BILL_HEAD_0026 + "\r\n");
            long total = 0L; // bankBillJd0026Service.total(param);
            int writeSize = 0;
            List<BankBillJd0026> billJd0026List = bankBillJd0026Service.queryExport(param);
            while (CollectionUtils.isNotEmpty(billJd0026List)) {
                writeSize = writeSize + billJd0026List.size();
                StringJoiner data = new StringJoiner("\r\n");
                for (BankBillJd0026 billJd0026 : billJd0026List) {
                    if (StringUtils.isNotBlank(billJd0026.getUserName())) {
                        billJd0026.setUserName(billJd0026.getUserName().replaceAll("\r|\n","").replaceAll(" ",""));
                    }
                    param.setMinId(null == param.getMinId() ? billJd0026.getId() : Math.max(param.getMinId(), billJd0026.getId())); // 设置最大ID，用于下次查询
                    data.add(objToCsvLine(billJd0026));
                }
                writeHttpResponse(response, os, data.toString() + "\r\n");
                billJd0026List = bankBillJd0026Service.queryExport(param);
                log.info("账单文件导出，共{}条记录，已导出{}条记录，文件名{}", total, writeSize, downName);
            }
        } catch (Exception e) {
            log.error("error : ", e);
            response.setStatus(HttpStatus.SYSTEM_BUSY);
        } finally {
            closeStream(null, os);
        }
    }

    /**
     * 导出账单_0027
     */
    @Override
    public void export0027(BankBillJd0027QueryParam param, HttpServletRequest request, HttpServletResponse response) throws ParseException, IOException {
        // 1、参数赋值与校验
        if (null == param || StringUtils.isBlank(param.getMerchantNo()) || StringUtils.isBlank(param.getSettleFinishTimeStart()) || StringUtils.isBlank(param.getSettleFinishTimeEnd())) {
            log.error("export0027参数错误");
            response.setStatus(HttpStatus.PARAM_ERROR);
            return;
        }
        if (DateUtil.dateDif(param.getSettleFinishTimeStart(), param.getSettleFinishTimeEnd()) > 31) {
            log.error("export0027只允许导出31天内的数据");
            response.setStatus(HttpStatus.PARAM_ERROR);
            return;
        }
        String downName = "shareFeeAccountWater_" + param.getSettleFinishTimeStart().replace("-", "") + "_" + param.getSettleFinishTimeEnd().replace("-", "") + ".csv";
        // 2、初始化 HttpServletResponse
        initHttpResponse(response, downName);
        // 3、循环写入数据 到 HttpServletResponse
        OutputStream os = response.getOutputStream();
        try {
            writeHttpResponse(response, os, Constants.JD_BILL_HEAD_0027 + "\r\n");
            long total = 0L; // bankBillJd0027Service.total(param);
            int writeSize = 0;
            List<BankBillJd0027> billJd0027List = bankBillJd0027Service.queryExport(param);
            while (CollectionUtils.isNotEmpty(billJd0027List)) {
                writeSize = writeSize + billJd0027List.size();
                StringJoiner data = new StringJoiner("\r\n");
                for (BankBillJd0027 billJd0027 : billJd0027List) {
                    if (StringUtils.isNotBlank(billJd0027.getUserName())) {
                        billJd0027.setUserName(billJd0027.getUserName().replaceAll("\r|\n","").replaceAll(" ",""));
                    }
                    param.setMinId(null == param.getMinId() ? billJd0027.getId() : Math.max(param.getMinId(), billJd0027.getId())); // 设置最大ID，用于下次查询
                    data.add(objToCsvLine(billJd0027));
                }
                writeHttpResponse(response, os, data.toString() + "\r\n");
                billJd0027List = bankBillJd0027Service.queryExport(param);
                log.info("账单文件导出，共{}条记录，已导出{}条记录，文件名{}", total, writeSize, downName);
            }
        } catch (Exception e) {
            log.error("error : ", e);
            response.setStatus(HttpStatus.SYSTEM_BUSY);
        } finally {
            closeStream(null, os);
        }
    }

    /**
     * 导出账单_0028
     */
    @Override
    public void export0028(BankBillJd0028QueryParam param, HttpServletRequest request, HttpServletResponse response) throws ParseException, IOException {

        // 1、参数赋值与校验
        if (null == param || StringUtils.isBlank(param.getMerchantNo()) || StringUtils.isBlank(param.getDealTimeStart()) || StringUtils.isBlank(param.getDealTimeEnd())) {
            log.error("export0028参数错误");
            response.setStatus(HttpStatus.PARAM_ERROR);
            return;
        }
        if (DateUtil.dateDif(param.getDealTimeStart(), param.getDealTimeEnd()) > 31) {
            log.error("export0028只允许导出31天内的数据");
            response.setStatus(HttpStatus.PARAM_ERROR);
            return;
        }
        String downName = "shareOrderCreate_" + param.getDealTimeStart().replace("-", "") + "_" + param.getDealTimeEnd().replace("-", "") + ".csv";
        // 2、初始化 HttpServletResponse
        initHttpResponse(response, downName);
        // 3、循环写入数据 到 HttpServletResponse
        OutputStream os = response.getOutputStream();
        try {
            writeHttpResponse(response, os, Constants.JD_BILL_HEAD_0028 + "\r\n");
            long total = 0L; // bankBillJd0028Service.total(param);
            int writeSize = 0;
            List<BankBillJd0028> billJd0028List = bankBillJd0028Service.queryExport(param);
            while (CollectionUtils.isNotEmpty(billJd0028List)) {
                writeSize = writeSize + billJd0028List.size();
                StringJoiner data = new StringJoiner("\r\n");
                for (BankBillJd0028 billJd0028 : billJd0028List) {
                    if (StringUtils.isNotBlank(billJd0028.getUserName())) {
                        billJd0028.setUserName(billJd0028.getUserName().replaceAll("\r|\n","").replaceAll(" ",""));
                    }
                    param.setMinId(null == param.getMinId() ? billJd0028.getId() : Math.max(param.getMinId(), billJd0028.getId())); // 设置最大ID，用于下次查询
                    data.add(objToCsvLine(billJd0028));
                }
                writeHttpResponse(response, os, data.toString() + "\r\n");
                billJd0028List = bankBillJd0028Service.queryExport(param);
                log.info("账单文件导出，共{}条记录，已导出{}条记录，文件名{}", total, writeSize, downName);
            }
        } catch (Exception e) {
            log.error("error : ", e);
            response.setStatus(HttpStatus.SYSTEM_BUSY);
        } finally {
            closeStream(null, os);
        }
    }

    /**
     * 转换成支持csv格式的数据
     */
    private static String transMsg(Object msg) {
        if (null == msg || StringUtils.isBlank(msg.toString())) {
            return "";
        }
        // 若为数值类型，则直接返回，否则进行特殊处理
        if (msg instanceof Number) {
            return msg.toString();
        }
        return "=\"" + msg.toString().replace(",", "，") + "\"";
    }

    /**
     * 转换商户号
     */
    private static String transMerchant(String merchantNo) {
        if (StringUtils.isBlank(merchantNo)) {
            return "";
        }
        if (JDBillService.JD_ACCOUNT_NO.equals(merchantNo) || JDBillService.JD_PLAT_NO.equals(merchantNo)) {
            return "快捷支付_" + merchantNo;
        } else if (JDBillService.JD_AGGRATE_ACCOUNT_NO.equals(merchantNo) || JDBillService.JD_AGGRATE_PLAT_NO.equals(merchantNo) ||JDBillService.JD_AGGRATE_ALIPAY_PLAT_NO.equals(merchantNo)) {
            return "聚合支付_" + merchantNo;
        }
        return merchantNo;
    }

    /**
     * 获取账单存储目录
     */
    public static String getDownPath(String date) {
        return ApolloUtil.getProperty("jd.pay.billDir", "/tmp/jdbill_download") + File.separator + date + File.separator;
    }

    /**
     * 获取 JDBillService 的实现类
     *
     * @param billType : 账单类型
     * @return
     */
    public static JDBillService getBillService(String billType) {
        return (JDBillService) SpringContextHolder.getApplicationContext().getBean("jDBillServiceImpl" + billType);
    }

    /**
     * 上报平安账单明细数据
     */
    private void fillPinganDetail(String logPrefix, String merchantNo, String downPath, String fileName, String billDate) throws Exception {
        long id = 0L;
        while (true) {
            List<BankBillJd0028> jd0028List = bankBillSubmitPinganService.submitPinganDetail(merchantNo, billDate, id);
            if (CollectionUtils.isEmpty(jd0028List)) {
                return;
            }
            StringJoiner detailLine = new StringJoiner("\r\n");
            for (BankBillJd0028 bankBillJd0028 : jd0028List) {
                // 获取最大id
                id = Math.max(id, bankBillJd0028.getId());
                // 生成内容
                detailLine.add(fillDetail(logPrefix, bankBillJd0028));
            }
            FileUtil.writeFile(downPath, fileName, detailLine.toString() + "\r\n", true);
        }
    }

    private String fillDetail(String logPrefix, BankBillJd0028 bankBillJd0028) throws Exception {
        StringJoiner msg = new StringJoiner(",");
        // 交易时间(YYYYMMDDHHMMSS)
        msg.add(bankBillJd0028.getTradeTime().replace("-", "").replace(" ", "").replace(":", "").replace(".0", "").trim());
        // 商户号(即外部平台在第三方支付渠道获分配的商户号)
        msg.add(bankBillJd0028.getMerchantNo());
        // 子商户号(暂不启用)
        msg.add("");
        if ("支付单".equals(bankBillJd0028.getOrderType()) || "退票".equals(bankBillJd0028.getOrderType())) {
            // 交易类型(0-支付交易，1-撤销交易，2-退款交易),
            msg.add("0");
            // 第三方支付渠道的支付交易的订单号或流水号
            msg.add(bankBillJd0028.getOrderNo());
            // 商户的支付交易的订单号或流水号
            msg.add(bankBillJd0028.getBankOrderNo());
            // 订单支付金额(即该笔支付交易的订单金额，保留2位小数，撤销交易或退款交易类型下此字段应为0.00)
            msg.add(bankBillJd0028.getOrderAmount().abs().toString());
            // 第三方支付渠道的撤销或退款交易的订单号或流水号
            msg.add("");
            // 商户的撤销或退款交易的订单号或流水号
            msg.add("");
            // 订单退款金额(即该笔撤销交易或退款交易的订单金额，保留2位小数的正数，支付交易类型下此字段应为0.00)
            msg.add("0.00");
            // 第三方支付渠道收取的手续费(支付交易此字段应为非负数,撤销交易或退款交易此字段应为非正数)
            //msg.add(bankBillJd0028.getFee().abs().toString());
            msg.add("0.00");
        } else if ("退款单".equals(bankBillJd0028.getOrderType())) {
            // 交易类型(0-支付交易，1-撤销交易，2-退款交易),
            msg.add("2");
            // 第三方支付渠道的支付交易的订单号或流水号
            msg.add(bankBillJd0028.getOriOrderNo());
            // 商户的支付交易的订单号或流水号
            msg.add(bankBillJd0028.getBankOrderNo());
            // 订单支付金额(即该笔支付交易的订单金额，保留2位小数，撤销交易或退款交易类型下此字段应为0.00)
            msg.add("0.00");
            // 第三方支付渠道的撤销或退款交易的订单号或流水号
            msg.add(bankBillJd0028.getOrderNo());
            // 商户的撤销或退款交易的订单号或流水号
            msg.add(bankBillJd0028.getBankOrderNo());
            // 订单退款金额(即该笔撤销交易或退款交易的订单金额，保留2位小数的正数，支付交易类型下此字段应为0.00)
            msg.add(bankBillJd0028.getOrderAmount().abs().toString());
            // 第三方支付渠道收取的手续费(支付交易此字段应为非负数,撤销交易或退款交易此字段应为非正数)
            //msg.add((bankBillJd0028.getFee().abs().multiply(new BigDecimal("-1"))).toString());
            msg.add("0.00");
        } else {
            log.error("{} [jdPay]账单处理: 报送平安异常：订单类型错误： {}", logPrefix, bankBillJd0028);
            throw new Exception("[jdPay]账单处理: 报送平安异常：订单类型错误： " + bankBillJd0028.getOrderType());
        }
        // 备注或附言信息,预留字段1,预留字段2,预留字段3
        msg.add("").add("").add("").add("");
        return msg.toString();
    }

    /**
     * 上报平安账单首行数据
     * 交易日期,商户号,支付交易总笔数,支付交易合计总金额,撤销交易总笔数,撤销交易合计总金额,退款交易总笔数,退款交易合计总金额
     */
    private String fillPinganHead(String logPrefix, String merchantNo, String downPath, String fileName, String beforeDate, String billDate) throws IOException {
        Map<String, BankBillJd0028Total> totalMap = bankBillSubmitPinganService.submitPinganTotal(merchantNo, billDate);
        StringJoiner head = new StringJoiner(",");
        head.add(beforeDate); // 交易日期
        head.add(merchantNo); // 商户号
        for (String tradeType : Arrays.asList("支付单", "撤销单", "退款单")) {
            if (totalMap.containsKey(tradeType)) {
                BankBillJd0028Total total = totalMap.get(tradeType);
                head.add(null == total.getTradeCount() ? "0" : total.getTradeCount().toString());
                head.add(null == total.getTradeAmount() ? "0" : total.getTradeAmount().abs().toString());
            } else {
                head.add("0");
                head.add("0");
            }
        }
        FileUtil.writeFile(downPath, fileName, head.toString() + "\r\n", true);
        return head.toString();
    }

    /**
     * 初始化 HttpServletResponse
     */
    @Override
    public void initHttpResponse(HttpServletResponse response, String downFileName) throws UnsupportedEncodingException {
        response.reset();
        //response.setHeader("Content-Type", "application/octet-stream");
        response.setContentType("application/octet-stream; charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-Disposition","attachment; filename=" + URLEncoder.encode(downFileName, "UTF-8"));
    }

    /**
     * 写入数据到 HttpServletResponse
     */
    @Override
    public void writeHttpResponse(HttpServletResponse response, OutputStream os, String data) throws IOException {
        BufferedInputStream bis = null;
        try {
            byte[] buff = new byte[1024];
            bis = new BufferedInputStream(new ByteArrayInputStream(data.getBytes()));
            int read = bis.read(buff);
            while (read != -1) {
                os.write(buff, 0, read);
                read = bis.read(buff);
            }
        } finally {
            closeStream(bis, null);
        }
    }

    /**
     * 关闭缓冲流
     */
    @Override
    public void closeStream(BufferedInputStream bis, OutputStream os) {
        if (bis != null) {
            try {
                bis.close();
            } catch (IOException e) { }
        }
        if (os != null) {
            try {
                os.flush();
                os.close();
            } catch (IOException e) { }
        }
    }

    /**
     * 将实体类转为csv行数据
     */
    private String objToCsvLine(Object obj) {
        StringJoiner line = new StringJoiner(",");
        if (obj instanceof BankBillJd0026) {
            BankBillJd0026 billJd0026 = (BankBillJd0026) obj;
            line.add(transMsg(transMerchant(billJd0026.getMerchantNo())));
            line.add(transMsg(billJd0026.getReceiveId()));
            line.add(transMsg(billJd0026.getReceiveName()));
            line.add(transMsg(billJd0026.getUserName()));
            line.add(transMsg(billJd0026.getYbmOrderNo()));
            line.add(transMsg(billJd0026.getSettleNo()));
            line.add(transMsg(billJd0026.getPlatMerchantNo()));
            line.add(transMsg(billJd0026.getDivideRecNo()));
            line.add(transMsg(billJd0026.getDivideMerchantNo()));
            line.add(transMsg(billJd0026.getDivideBusinessNo()));
            line.add(transMsg(billJd0026.getAmount()));
            line.add(transMsg(billJd0026.getTradeCreateTime()));
            line.add(transMsg(billJd0026.getSettleFinishTime()));
            line.add(transMsg(billJd0026.getFee()));
            line.add(transMsg(billJd0026.getRemark()));
            line.add(transMsg(billJd0026.getOriPlatMerchantNo()));
            line.add(transMsg(billJd0026.getOriDivideMerchantNo()));
            line.add(transMsg(billJd0026.getPayStyle()));
            line.add(transMsg(billJd0026.getOrderType()));
            line.add(transMsg(billJd0026.getCardType()));
            line.add(transMsg(billJd0026.getIssuerBank()));
            line.add(transMsg(billJd0026.getPayNo()));
            line.add(transMsg(billJd0026.getCapitalType()));
            line.add(transMsg(billJd0026.getFeeNo()));
            line.add(transMsg(billJd0026.getFeeAccount()));
            return line.toString();
        }
        if (obj instanceof BankBillJd0027) {
            BankBillJd0027 billJd0027 = (BankBillJd0027) obj;
            line.add(transMsg(transMerchant(billJd0027.getMerchantNo())));
            line.add(transMsg(billJd0027.getReceiveId()));
            line.add(transMsg(billJd0027.getReceiveName()));
            line.add(transMsg(billJd0027.getUserName()));
            line.add(transMsg(billJd0027.getYbmOrderNo()));
            line.add(transMsg(billJd0027.getSettleNo()));
            line.add(transMsg(billJd0027.getPayNo()));
            line.add(transMsg(billJd0027.getInTransitMerchantNo()));
            line.add(transMsg(billJd0027.getPlatMerchantNo()));
            line.add(transMsg(billJd0027.getPlatBusinessNo()));
            line.add(transMsg(billJd0027.getOrderAmount()));
            line.add(transMsg(billJd0027.getSettleFinishTime()));
            line.add(transMsg(billJd0027.getFee()));
            line.add(transMsg(billJd0027.getOriPlatMerchantNo()));
            line.add(transMsg(billJd0027.getPayStyle()));
            line.add(transMsg(billJd0027.getOrderType()));
            line.add(transMsg(billJd0027.getCardType()));
            line.add(transMsg(billJd0027.getIssuerBank()));
            return line.toString();
        }
        if (obj instanceof BankBillJd0028) {
            BankBillJd0028 billJd0028 = (BankBillJd0028) obj;
            line.add(transMsg(transMerchant(billJd0028.getMerchantNo())));
            line.add(transMsg(billJd0028.getReceiveId()));
            line.add(transMsg(billJd0028.getReceiveName()));
            line.add(transMsg(billJd0028.getUserName()));
            line.add(transMsg(billJd0028.getYbmOrderNo()));
            line.add(transMsg(billJd0028.getOrderNo()));
            line.add(transMsg(billJd0028.getBankOrderNo()));
            line.add(transMsg(billJd0028.getOrderAmount()));
            line.add(transMsg(billJd0028.getBusinessType()));
            line.add(transMsg(billJd0028.getOrderType()));
            line.add(transMsg(billJd0028.getOrderStatus()));
            line.add(transMsg(billJd0028.getRefundAmount()));
            line.add(transMsg(billJd0028.getSettleStatus()));
            line.add(transMsg(billJd0028.getTradeTime()));
            line.add(transMsg(billJd0028.getDealTime()));
            line.add(transMsg(billJd0028.getOriOrderNo()));
            line.add(transMsg(billJd0028.getFee()));
            line.add(transMsg(billJd0028.getAccountName()));
            line.add(transMsg(billJd0028.getBankName()));
            line.add(transMsg(billJd0028.getBusinessOrderNo()));
            line.add(transMsg(billJd0028.getTradeNo()));
            line.add(transMsg(billJd0028.getPlatMerchantNo()));
            line.add(transMsg(billJd0028.getPlatMerchant()));
            line.add(transMsg(billJd0028.getPlatMerchantName()));
            line.add(transMsg(billJd0028.getPlatInTransitMerchant()));
            line.add(transMsg(billJd0028.getSubMerchant()));
            line.add(transMsg(billJd0028.getCommissionMsg()));
            line.add(transMsg(billJd0028.getMarketingMsg()));
            line.add(transMsg(billJd0028.getCommissionAmount()));
            line.add(transMsg(billJd0028.getRemark()));
            return line.toString();
        }
        throw new RuntimeException("类型错误！");
    }

}
